import processing.core.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.Random;
import javax.swing.Timer;;


/**
 * <code>SocialIdentity</code> is the main class for SocialIdentity application. It draws a 
 * window and asks user to click to sign in to his/hers Facebook account. User's profile 
 * image is drawn and friends' images downloaded after the next mouse click. When the 
 * loading is ready user may start using the SocialIdentity.
 * <p>
 * SocialIdentity creates a mosaic using one's Facebook friends. User may zoom from his/hers 
 * own profile image first to mosaic with 100 friend on each side of the mosaic, then to 25 
 * and finally to 10. After final 10*10 mosaic user selects a new picture and the mosaic 
 * drawing loop starts from the beginning.
 * 
 * The window is updated with the help of an <code>int</code> type user <code>state</code> 
 * that is increased or decreased according to clicks.
 * 
 * <u>state	|	function</u>
 * 0		|	status message is drawn and connecting to Facebook is tried
 * 1		|	status message is drawn and user's profile image downloaded
 * 2		|	status message and users profile image is drawn and friends' images downloaded  
 * 3		|	user's profile image is shown and status message updates during the images download 
 * 4		|	the first 100*100 matrix is created
 * 5		|	the 25*25 matrix is created and 100*100 matrix is drawn
 * 6		|	the 10*10 matrix is created and 25*25 matrix is drawn
 * 7		|	the 10*10 matrix is drawn and the next user is selected, state is reduced to 4
 * 
 */
@SuppressWarnings("serial")
public class SocialIdentity extends PApplet implements ActionListener {

	private ImageCatalog catalog;
	private ProfileImage loginUser;
	private ProfileImage currentUser;
	private ProfileImage nextUser;
	private Matrix matrix;
	private FBConnect client;
	private PFont font;
	private int state;
	private int rectX, rectY;
	private int rectSize;
	private int imageX, imageY;
	private PImage image;
	private String statusMessage;

	private Timer timer;

	private Timer move; 
	
	private int xTarget, yTarget;
	private int startX, startY;
	private int rectStep;
	
	public void setup() {
		size(500,500); 
		this.font = createFont("Arial",16);
		this.textFont(font); 
		this.textAlign(CENTER);
		this.stroke(255);
		this.state = 0;
		this.setStatusMessage("Click to login to Facebook");
		this.timer = new Timer(10000, this);
	} 

	public void draw(){

		//this.clearScreen();

		switch(this.state){

		case 0: // Start FB login on click
			this.clearScreen();
			this.drawStatusMessage();
			break;

		case 1: // Waiting for FB login
			this.clearScreen();
			this.drawStatusMessage();
			break;

		case 2: // Loading user picture
			this.clearScreen();
			this.drawStatusMessage();

			this.loginUser = this.client.getUserImage();
			if (this.loginUser != null){
				this.currentUser = loginUser;
				this.client.loadImageCatalog();
				this.state++;
			}		
			break;

		case 3: // Waiting for ImageCatalog to load
			this.clearScreen();
			this.drawImage();
			int percent = this.client.loadingPercent();
			float blurAmount = map(100-percent,0,100,0,20);
			filter(BLUR, blurAmount);
			this.setStatusMessage("Loading your friends: " + Integer.toString(percent) + "%");
			this.drawStatusMessage();

			this.catalog = this.client.getImageCatalog();
			if (this.catalog != null){
				this.catalog.sortImages();
				//this.timer.start();
				//System.out.println("timer started");
				this.state++;
			}
			break;

		case 4: // Show user's big image
			this.clearScreen();
			this.drawImage();
			this.setRect(100, 0);
			this.updateRect();
			break;

		case 5: // Show biggest matrix
			if (!this.matrix.isZooming()) this.clearScreen();
			this.matrix.drawMatrix();
			if (!this.matrix.isZooming()) this.updateRect();
			break;

		case 6: // Showing medium matrix
			if (!this.matrix.isZooming()) this.clearScreen();
			this.matrix.drawMatrix();
			if (!this.matrix.isZooming()) this.updateRect();
			break;

		case 7: // Show small matrix
			if (!this.matrix.isZooming()) this.clearScreen();
			this.matrix.drawMatrix();
			if (!this.matrix.isZooming()) this.updateRect();

			// if next user is defined and user's big image loaded

			if (this.nextUser != null){
				PImage image = this.nextUser.getPic_big();
				if (image != null){
					this.currentUser = this.nextUser;
					this.nextUser = null;
					this.state = 4;
				}
			}
			break;
		}		
	}

	/**
	 * Events on mouseClick
	 */	
	public void mouseClicked() {

//		if (this.timer.isRunning()) {
//			this.timer.stop();
//		}

		switch(this.state){

		case 0:
			this.client = new FBConnect(this);
			if(this.client.login()){
				this.setStatusMessage("Click the window after you have logged in to Facebook and allowed this app to read your profile data");
				this.state++;
			} else{
				this.setStatusMessage("Could not login. Click to try again.");
			}

			break;

		case 1:
			if(this.client.startSession()){
				this.client.loadUserImage();
				this.setStatusMessage("Loading your profile image");
				this.state++;
			} else {
				this.setStatusMessage("Starting session failed. Click to try again.");
			}
			break;

		case 4: // generate matrix on click
			this.matrix = new Matrix(this, catalog);
			this.matrix.createMatrix(100,rectX,rectY,rectSize);
			this.matrix.setZoomSize(100, this.rectX, this.rectY, this.rectSize, 20);
			this.setRect(125,100);
			//this.timer.start();
			this.state++;
			break;

		case 5:
			this.matrix.setZoomSize(25, this.rectX, this.rectY, this.rectSize, 20);
			this.setRect(200, 25);
			//this.timer.start();
			this.state++;
			break;

		case 6:
			this.matrix.setZoomSize(10, this.rectX, this.rectY, this.rectSize, 20);
			this.setRect(50, 10);
			//this.timer.start();
			this.state++;
			break;

		case 7: // get clicked user from matrix and start to load user's big image
			this.nextUser = this.matrix.getUser(mouseX, mouseY);
			this.client.loadBigImage(nextUser);
			//this.timer.start();
			break;

		}
	}

	/**
	 * If the mouse moves the timer needs to be restarted to prevent the automatic zoom.
	 */
	public void mouseMoved() {
		if (this.state >= 4) {
			//this.timer.restart();
			this.rectX = mouseX-rectSize/2;
			this.rectY = mouseY-rectSize/2;
			this.updateRect();
		}
	}

	/**
	 * Clear the canvas
	 */
	public void clearScreen(){
		background(100);
	}

	/**
	 * Draws current user's big image centered to screen
	 * @param image
	 */
	public void drawImage(){
		image = this.currentUser.getPic_big();
		imageX = (this.width-image.width)/2;
		imageY = (this.height-image.height)/2;
		this.image(image,imageX,imageY);
		text(this.currentUser.getName(),(this.width/2)-200,this.height-50,400,50);
	}
	
	public void setRect(int size, int steps){
		this.rectSize = size;
		if (steps != 0){
			this.rectStep = this.height/steps;
		} else {
			this.rectStep = 0;
		}
	}

	/**
	 * TODO comment
	 * @param rectSize
	 */
	public void updateRect(){
		
		this.checkRectSteps();
		this.checkRectBounds();

		noFill();
		// Draw the actual rect 1px over the size to avoid borders to show up in matrix
		rect(rectX-1,rectY-1,rectSize+2,rectSize+2);
	}

	/**
	 * round rect coordinates to match the matrix cell steps
	 */
	public void checkRectSteps(){
		if (this.rectStep > 0){
			int moduloX = this.rectX%this.rectStep;
			if (moduloX != 0){
				this.rectX -= moduloX;
			}
			int moduloY = this.rectY%this.rectStep;
			if (moduloY != 0){
				this.rectY -= moduloY;
			}
		}
	}
	
	public void checkRectBounds() {
		int leftbound, rightbound, topbound, bottombound;

		switch (state){
		case 4:
			leftbound = imageX;
			rightbound = imageX+image.width-rectSize;
			topbound = imageY;
			bottombound = imageY+image.height-rectSize;
			break;
		default:
			leftbound = 0;
			rightbound = this.width-rectSize;
			topbound = 0;
			bottombound = this.height-rectSize;
		}
		
		if (rectX < leftbound){
			rectX = leftbound;
		} else if (rectX > rightbound){
			rectX = rightbound;
		}

		if (rectY < topbound){
			rectY = topbound;
		} else if (rectY > bottombound){
			rectY = bottombound;
		}
		
		//same for target coordinates
		if (xTarget < leftbound){
			xTarget = leftbound;
		} else if (xTarget > rightbound){
			xTarget = rightbound;
		}

		if (yTarget < topbound){
			yTarget = topbound;
		} else if (yTarget > bottombound){
			yTarget = bottombound;
		}
	}

	public void setStatusMessage(String message){
		this.statusMessage = message;
	}

	public void drawStatusMessage(){
		text(this.statusMessage,(this.width/2)-200,50,400,100);
	}

	@Override
	public void actionPerformed(ActionEvent e) {
		this.timer.stop();
		Random rand = new Random();

		this.xTarget = rand.nextInt(500 - this.rectSize);
		this.yTarget = rand.nextInt(500 - this.rectSize);
		this.checkRectBounds();
		
		this.startX = this.rectX;
		this.startY = this.rectY;
		
		this.move = new Timer(20, new ActionListener(){
			public void actionPerformed(ActionEvent e) {
				int rectXend = rectX;
				int rectYend = rectY;
				
				int addX = 1;
				int addY = 1;
				while (rectXend != xTarget || rectYend != yTarget) {
					addX = xTarget - rectX;
					if (addX <= 5) {
						rectX = xTarget;
					} else if (addX > 0 && matrix != null) {
						rectX += rectSize / SocialIdentity.this.matrix.getSize();
					} else if(matrix != null){
						rectX -= rectSize / SocialIdentity.this.matrix.getSize();
					} else {
						addX = addX / 5;
						if (addX > 0) {
							rectX += addX;
						} else {
							rectX = xTarget;
						}
					}
					
					addY = yTarget - rectY;
					if (addY <= 5) {
						rectY = yTarget;
					} else if (addY > 0 && matrix != null) {
						rectY += rectSize / SocialIdentity.this.matrix.getSize();
					} else if(matrix != null){
						rectY -= rectSize / SocialIdentity.this.matrix.getSize();
					} else {
						addY = addY / 5;
						if (addY > 0) {
							rectY += addY;
						} else {
							rectY = yTarget;
						}
					}
					
					rectXend = rectX;
					rectYend = rectY;
					
					System.out.println("x: " + rectXend + " y: " + rectYend + " target x: " + xTarget + " target y: " + yTarget + " start x: " + startX + " start y: " + startY + " addx: " + addX + " addy: " + addY);
					
					SocialIdentity.this.updateRect();
				}
				
				SocialIdentity.this.move.stop();
				SocialIdentity.this.mouseClicked();
			}
		});
		
		this.move.start();
	}

	/**
	 * Main method for desktop mode
	 * @param args
	 */
	public static void main(String args[]){
		PApplet.main(new String[] { SocialIdentity.class.getName()});
	}
	
}
